﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TetrisService
{
    /// <summary>
    /// this class is the heart of the code, all of Tetrominos extend this class. It is responsable to move the piece at the left, right and down.
    /// In addition, it also provides methods to avoid colisions. 
    /// </summary>
    public abstract class Tetromino
    {
        protected readonly static int CLEAN_FIELD = 0;
        protected static readonly int BLOCK_0 = 0;
        protected static readonly int BLOCK_1 = 1;
        protected static readonly int BLOCK_2 = 2;
        protected static readonly int BLOCK_3 = 3;

        protected int[][] board;
        protected TetrominoBlock[] tetromino;

        /// <summary>
        /// Initializes a new instance of the <see cref="Tetromino"/> class.
        /// </summary>
        /// <param name="board">The board.</param>
        protected Tetromino(int[][] board)
        {
            this.board = board;
            this.tetromino = new TetrominoBlock[4];
        }

        /// <summary>
        /// Gets the current tetromino which is made by the four blocks.
        /// </summary>
        /// <returns></returns>
        public TetrominoBlock[] getTetromino()
        {
            return tetromino;
        }

        /// <summary>
        /// Determines if the coordinates are inside the board.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns>
        ///   <c>true</c> if (x,y) belongs to the board; otherwise, <c>false</c>.
        /// </returns>
        private bool isInsideBoard(int x, int y)
        {
            return (x >= 0 && x < board[0].Length) &&
                (y >= 0 && y < board.Length);
        }

        /// <summary>
        /// Determines if the position (x,y) is occupied by another block.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns>
        ///   <c>true</c> if the position (x,y) holds another block; otherwise, <c>false</c>.
        /// </returns>
        protected bool hasColisionInThisBlock(int x, int y)
        {
            if (!isInsideBoard(x, y))
            {
                return true;
            }
            else if (board[y][x] > CLEAN_FIELD)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Determines if the position provided is not on the end of the game
        /// board (the lower possible position, or the ground).
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns>
        ///   <c>true</c> if the block on the position (x,y) is still falling; otherwise, <c>false</c>.
        /// </returns>
        private bool isBlockFalling(int x, int y)
        {
            return (x >= 0 && x < board[0].Length) &&
                (y < board.Length);
        }

        /// <summary>
        /// Determines if has colision with the part of the block that is inside the board with the specified x.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <returns>
        ///   <c>true</c> if has colision in incomplete block; otherwise, <c>false</c>.
        /// </returns>
        protected bool hasColisionInIncompleteBlock(int x, int y)
        {
            if (!isBlockFalling(x, y))
            {
                return true;
            }
            else if (y < 0)
            {
                return false;
            }
            else if (board[y][x] > CLEAN_FIELD)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Determines if the tetromino is completely inside the game board.
        /// </summary>
        /// <param name="block">The block.</param>
        /// <returns>
        ///   <c>true</c> if is block formed; otherwise, <c>false</c>.
        /// </returns>
        private bool isBlockFormed(TetrominoBlock block)
        {
            return block.getY() >= 0 && block.getX() >= 0 && block.getX() < board.Length;
        }

        /// <summary>
        /// Determines whether this instance can move right.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this instance can move right; otherwise, <c>false</c>.
        /// </returns>
        protected bool canMoveRight()
        {
            for (int i = 0; i < tetromino.Length; i++)
            {
                if (tetromino[i].getX() >= board[0].Length - 1)
                {
                    return false;
                }
                else if (!isBlockFormed(tetromino[i]))
                {
                    continue;
                }
                else if (board[tetromino[i].getY()][tetromino[i].getX() + 1] > CLEAN_FIELD)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Determines whether this instance can move left.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this instance can move left; otherwise, <c>false</c>.
        /// </returns>
        protected bool canMoveLeft()
        {
            for (int i = 0; i < tetromino.Length; i++)
            {
                if (tetromino[i].getX() <= 0)
                {
                    return false;
                }
                else if (!isBlockFormed(tetromino[i]))
                {
                    continue;
                }
                else 
                if (board[tetromino[i].getY()][tetromino[i].getX() - 1] > CLEAN_FIELD)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Determines whether this instance can move down.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this instance can move down; otherwise, <c>false</c>.
        /// </returns>
        protected bool canMoveDown()
        {
            for (int i = 0; i < tetromino.Length; i++)
            {
                if (tetromino[i].getY() + 1 >= board.Length )
                {
                    return false;
                }
                else if (!isBlockFormed(tetromino[i]))
                {
                    continue;
                }
                else if (board[tetromino[i].getY() + 1][tetromino[i].getX()] > CLEAN_FIELD)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Clear the current tetromino.
        /// </summary>
        protected void clearCurrentTetromino()
        {
            for (int i = 0; i < tetromino.Length; i++)
            {
                if (!hasColisionInThisBlock(tetromino[i].getX(), tetromino[i].getY()))
                {
                    board[tetromino[i].getY()][tetromino[i].getX()] = CLEAN_FIELD;
                }
            }
        }

        /// <summary>
        /// Draws the current tetromino in the game board.
        /// </summary>
        /// <param name="field">The field.</param>
        protected void fillCurrentTetromino(int field)
        {
            if (board != null)
            {
                for (int i = 0; i < tetromino.Length; i++)
                {
                    if (!hasColisionInThisBlock(tetromino[i].getX(), tetromino[i].getY()))
                    {
                        board[tetromino[i].getY()][tetromino[i].getX()] = field;
                    }
                }
            }
        }

        /// <summary>
        /// Creates and returns a copy of the piece.
        /// </summary>
        /// <param name="thisPiece">The this piece.</param>
        /// <returns></returns>
        protected TetrominoBlock[] copyPiece(TetrominoBlock[] thisPiece)
        {
            TetrominoBlock[] newPiece = new TetrominoBlock[thisPiece.Length];
            for (int i = 0; i < thisPiece.Length; i++)
            {
                newPiece[i] = new TetrominoBlock(thisPiece[i].getX(), thisPiece[i].getY());
            }
            return newPiece;
        }

        /// <summary>
        /// Moves the piece to the left.
        /// </summary>
        /// <param name="temporaryTetromino">The tetromino that is falling.</param>
        protected void moveLeft(TetrominoBlock[] temporaryTetromino)
        {
            for (int i = 0; i < temporaryTetromino.Length; i++)
            {
                temporaryTetromino[i].addX(-1);
            }
        }

        /// <summary>
        /// Moves the piece to the right.
        /// </summary>
        /// <param name="temporaryTetromino">The tetromino that is falling.</param>
        protected void moveRight(TetrominoBlock[] temporaryTetromino)
        {
            for (int i = 0; i < temporaryTetromino.Length; i++)
            {
                temporaryTetromino[i].addX(1);
            }
        }

        /// <summary>
        /// Moves down the piece that is falling.
        /// </summary>
        /// <param name="temporaryTetromino">The TMP piece.</param>
        protected void moveDown(TetrominoBlock[] temporaryTetromino)
        {
            for (int i = 0; i < tetromino.Length; i++)
            {
                tetromino[i].addY(1);
            }
        }

        /// <summary>
        /// Determines whether the specified temporary tetromino collides with another block.
        /// </summary>
        /// <param name="temporaryTetromino">The temporary tetromino.</param>
        /// <returns>
        ///   <c>true</c> if the specified temporary tetromino collides with another block; otherwise, <c>false</c>.
        /// </returns>
        protected bool hasColision(TetrominoBlock[] temporaryTetromino)
        {
            for (int i = 0; i < temporaryTetromino.Length; i++)
            {
                if(temporaryTetromino[i].getX() >= board[0].Length ||
                    temporaryTetromino[i].getX() < 0) {
                    return true;
                }
                else if (!isBlockFormed(temporaryTetromino[i]))
                {
                    continue;
                }
                else if (temporaryTetromino[i].getY() >= board.Length)
                {
                    return true;
                }
                else if (board[temporaryTetromino[i].getY()][temporaryTetromino[i].getX()] != CLEAN_FIELD)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Updates the position of the tetromino.
        /// </summary>
        /// <param name="copyOfTheTetromino">The copy of the tetromino.</param>
        /// <returns></returns>
        protected bool updateTetrominoPosition(TetrominoBlock[] copyOfTheTetromino)
        {
            if (!hasColision(copyOfTheTetromino))
            {
                tetromino = copyOfTheTetromino;
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Check if it is possible to move to the right. If possible, it will call
        /// the method which will actually move the tetromino.
        /// </summary>
        /// <returns></returns>
        public bool moveRight()
        {
            if (!canMoveRight())
            {
                return false;
            }
            clearCurrentTetromino();
            moveRight(tetromino);
            fillCurrentTetromino(colorWhileFalling());
            return true;
        }

        /// <summary>
        /// Check if it is possible to move to the left. If possible, it will call
        /// the method which will actually move the tetromino.
        /// </summary>
        /// <returns></returns>
        public bool moveLeft()
        {
            if (!canMoveLeft())
            {
                return false;
            }
            clearCurrentTetromino();
            moveLeft(tetromino);
            fillCurrentTetromino(colorWhileFalling());
            return true;
        }

        /// <summary>
        /// Check if it is possible to move the tetromino down. If possible, it will call
        /// the method which will actually move the tetromino.
        /// </summary>
        /// <returns></returns>
        public bool moveDown()
        {
            if (!canMoveDown())
            {
                fillCurrentTetromino(color());
                return false;
            }
            clearCurrentTetromino();
            moveDown(tetromino);
            fillCurrentTetromino(colorWhileFalling());
            return true;
        }

        /// <summary>
        /// Rotates the tetromino clockwisely.
        /// </summary>
        /// <returns></returns>
        public abstract bool rotate();

        /// <summary>
        /// Returns the color of the tetromino.
        /// </summary>
        /// <returns></returns>
        public abstract int color();

        /// <summary>
        /// When a line is done by the player, it should be erased. Because of that, we decided
        /// to represent the piece that is falling down by a different state, which is represented
        /// by a color, so when we move down all the blocks above the line that was made, the
        /// tetromino that is falling should not move down.
        /// </summary>
        /// <returns></returns>
        protected abstract int colorWhileFalling();
    }
}